package com.powernode.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.powernode.domain.Prod;
import com.powernode.domain.ProdComm;
import com.powernode.domain.ProdTagReference;
import com.powernode.domain.Sku;
import com.powernode.ex.ProductException;
import com.powernode.mapper.ProdMapper;
import com.powernode.mapper.SkuMapper;
import com.powernode.model.ProdEs;
import com.powernode.model.ProdEsChange;
import com.powernode.model.SkuChange;
import com.powernode.model.StockChange;
import com.powernode.service.ProdCommService;
import com.powernode.service.ProdService;
import com.powernode.service.ProdTagReferenceService;
import com.powernode.service.SkuService;
import com.powernode.utils.AuthUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 内容:
 * 作者: cy
 */
@Service
@Slf4j
public class ProdServiceImpl extends ServiceImpl<ProdMapper, Prod> implements ProdService {

    @Autowired
    private ProdMapper prodMapper;

    @Autowired
    private SkuService skuService;
    @Autowired
    private SkuMapper skuMapper;

    @Autowired
    private ProdTagReferenceService prodTagReferenceService;

    @Autowired
    private ProdCommService prodCommService;


    /**
     * 分页查询商品列表
     *
     * @param page
     * @param prod
     * @return
     */
    @Override
    public Page<Prod> selectProdPage(Page<Prod> page, Prod prod) {
        Long shopId = AuthUtil.getUserIdOrShopId(false);
        return prodMapper.selectPage(page, new LambdaQueryWrapper<Prod>()
                .eq(shopId != 1, Prod::getShopId, shopId)
                .eq(!ObjectUtils.isEmpty(prod.getStatus()), Prod::getStatus, prod.getStatus())
                .like(StringUtils.hasText(prod.getProdName()), Prod::getProdName, prod.getProdName())
                .orderByDesc(Prod::getCreateTime)
        );
    }


    /**
     * 新增商品
     * 1.prod   1
     * 2.sku   多
     * 3.tag_reference  标签关系表
     * ----------------------
     * spring 没有事务 代理数据库的事务   aop    代理对象
     * 怎么代理的？？？？？？
     * connection.setAutoCommit(false);
     * connection.doCommit();
     * connection.doRollback();
     * 事务几种传播特性 7种
     * 事务的隔离级别有 4种
     *
     * @param prod
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void saveProd(Prod prod) {
        Long userId = AuthUtil.getUserIdOrShopId(true);
        Long shopId = AuthUtil.getUserIdOrShopId(false);
        log.info("新增商品{}，用户id为{}，店铺id为{}", JSON.toJSONString(prod), userId, shopId);
        prod.setCreateTime(new Date());
        prod.setUpdateTime(new Date());
        prod.setShopId(shopId);
        prod.setSoldNum(0);
        prod.setVersion(0);
        if (prod.getStatus().equals(1)) {
            prod.setPutawayTime(new Date());
        }
        Prod.DeliveryModeVo deliveryModeVo = prod.getDeliveryModeVo();
        prod.setDeliveryMode(JSON.toJSONString(deliveryModeVo));
        // 插入prod表
        int i = prodMapper.insert(prod);
        if (i > 0) {
            // 处理sku
            handlerSku(prod.getSkuList(), prod.getProdId());
            // 处理标签关系表
            handlerTagReference(prod.getTagList(), prod.getProdId(), shopId);
        }
    }

    /**
     * 处理标签关系表
     *
     * @param tagList
     * @param prodId
     * @param shopId
     */
    public void handlerTagReference(List<Long> tagList, Long prodId, Long shopId) {
        if (CollectionUtils.isEmpty(tagList)) {
            return;
        }
        ArrayList<ProdTagReference> prodTagReferences = new ArrayList<>(tagList.size());
        tagList.forEach(tId -> {
            ProdTagReference prodTagReference = new ProdTagReference();
            prodTagReference.setCreateTime(new Date());
            prodTagReference.setProdId(prodId);
            prodTagReference.setShopId(shopId);
            prodTagReference.setStatus(true);
            prodTagReference.setTagId(tId);
            prodTagReferences.add(prodTagReference);
        });
        boolean b = prodTagReferenceService.saveBatch(prodTagReferences);
        if (!b) {
            log.error("新增标签关系失败{}", JSON.toJSONString(tagList));
            throw new ProductException("新增标签关系失败");
        }
    }

    /**
     * 处理sku
     *
     * @param skuList
     * @param prodId
     */
    private void handlerSku(List<Sku> skuList, Long prodId) {
        if (CollectionUtils.isEmpty(skuList)) {
            return;
        }
        skuList.forEach(sku -> {
            sku.setProdId(prodId);
            sku.setRecTime(new Date());
            sku.setUpdateTime(new Date());
            sku.setIsDelete(0);
            sku.setVersion(0);
        });
        // 插入这个表
        boolean b = skuService.saveBatch(skuList);
        if (!b) {
            log.error("新增sku失败{}", JSON.toJSONString(skuList));
            throw new ProductException("新增sku失败");
        }
    }


    /**
     * 查询时间段内的商品数量
     *
     * @param t1
     * @param t2
     * @return
     */
    @Override
    public int selectCount(Date t1, Date t2) {
        return prodMapper.selectCount(new LambdaQueryWrapper<Prod>()
                .between(t1 != null && t2 != null, Prod::getUpdateTime, t1, t2)
        );
    }


    /**
     * 查询数据 组装符合条件的商品对象
     * 1.分页查询商品表
     * 2.去组装标签关系list  完善好评的数据
     * 3.组装返回
     *
     * @param page
     * @param t1
     * @param t2
     * @return
     */
    @Override
    public List<ProdEs> selectProdByEs(Page<Prod> page, Date t1, Date t2) {
        long size = page.getSize();
        // 先创建返回值
        List<ProdEs> prodEsList = new ArrayList<>((int) size);
        Page<Prod> prodPage = prodMapper.selectPage(page, new LambdaQueryWrapper<Prod>()
                .between(t1 != null && t2 != null, Prod::getUpdateTime, t1, t2)
        );
        List<Prod> prodList = prodPage.getRecords();
        if (CollectionUtils.isEmpty(prodList)) {
            return prodEsList;
        }
        // 组装 标签关系
        // 先拿到所有的商品的ids
        List<Long> prodIds = prodList.stream()
                .map(Prod::getProdId)
                .collect(Collectors.toList());
        // 查询标签关系中间表
        List<ProdTagReference> tagReferenceList = prodTagReferenceService.list(new LambdaQueryWrapper<ProdTagReference>()
                .in(ProdTagReference::getProdId, prodIds)
        );
        // 循环商品集合
        prodList.forEach(prod -> {
            ProdEs prodEs = new ProdEs();
            // 将prod数据 拷贝到 prodEs
            BeanUtil.copyProperties(prod, prodEs, true);
            // 过滤出符合条件的标签ids
            List<Long> tagList = tagReferenceList.stream()
                    .filter(prodTagReference -> prodTagReference.getProdId().equals(prod.getProdId()))
                    .map(ProdTagReference::getTagId)
                    .collect(Collectors.toList());
            prodEs.setTagList(tagList);
            prodEsList.add(prodEs);
        });
        // 处理评论 循环prodEsList  拿总评数 拿好评数    好评率 =   好评数/总评数
        // 根据prodIds 查询评论表
        // select * from
        List<ProdComm> prodCommList = prodCommService.list(new LambdaQueryWrapper<ProdComm>()
                .select(ProdComm::getProdId, ProdComm::getEvaluate) // 就是查询哪些字段
                .in(ProdComm::getProdId, prodIds)
        );
        // 循环prodEsList
        prodEsList.forEach(prodEs -> {
            // 循环评论集合 过滤出符合条件的该商品的评论集合
            List<ProdComm> oneProdCommList = prodCommList.stream()
                    .filter(prodComm -> prodComm.getProdId().equals(prodEs.getProdId()))
                    .collect(Collectors.toList());
            int totalSize = oneProdCommList.size();
            if (totalSize > 0) {
                // 拿好评
                long goodsNum = oneProdCommList.stream()
                        .filter(prodComm -> prodComm.getEvaluate().equals(0))
                        .count();
                if (goodsNum > 0) {
                    // 计算好评率
                    BigDecimal goodLv = new BigDecimal(goodsNum)
                            .divide(new BigDecimal(totalSize), 2, BigDecimal.ROUND_HALF_UP)
                            .multiply(new BigDecimal(100));
                    // 设置
                    prodEs.setPraiseNumber(goodsNum);
                    prodEs.setPositiveRating(goodLv);
                }
            }
        });
        return prodEsList;
    }

    /**
     * 根据prodId查询prod信息和sku信息
     *
     * @param prodId
     * @return
     */
    @Override
    public Prod selectProdAndSkuByProdId(Long prodId) {
        Prod prod = prodMapper.selectById(prodId);
        // 查sku表
        List<Sku> list = skuService.list(new LambdaQueryWrapper<Sku>()
                .eq(Sku::getProdId, prod.getProdId())
                .eq(Sku::getStatus, 1)
        );
        prod.setSkuList(list);
        return prod;
    }
    /**
     * 修改prod和sku的库存
     * 批量修改 目前没有好的方案 只能在循环里面发sql
     *
     * @param stockChange
     * @return
     */
    @Override
    @Transactional
    public Boolean changeStock(StockChange stockChange) {
        List<ProdEsChange> prodEsChanges = stockChange.getProdEsChanges();
//        prodEsChanges.forEach(prodEsChange -> {
//            Long prodId = prodEsChange.getProdId();
//            Integer count = prodEsChange.getCount();
//            // 这里可以去查 然后 设置 然后 update   我们不建议在循环中操作数据库
//
//        });
        // 线程安全问题  每个线程有自己的工作空间 导致有缓存
        // 先查 再改 再更新 就有线程安全问题
//        List<Long> prodIds = prodEsChanges.stream()
//                .map(ProdEsChange::getProdId)
//                .collect(Collectors.toList());

        // redis  key  prodId    ????   setnx     setIfAbsent  如果有多个商品需要修改 就得循环给每个id加锁
        // 1,2,3      1-2-3   =?   1-3-4
//        synchronized (this) {
//            List<Prod> prodList = prodMapper.selectBatchIds(prodIds);
//            prodList.forEach(prod -> {
//                ProdEsChange prodEsChange1 = prodEsChanges.stream().filter(prodEsChange -> prodEsChange.getProdId().equals(prod.getProdId())).collect(Collectors.toList()).get(0);
//                int finalCount = prod.getTotalStocks() + prodEsChange1.getCount();
//                if (finalCount < 0) {
//                    throw new IllegalArgumentException("xxxx");
//                }
//                prod.setTotalStocks(finalCount);
//            });
//            boolean b = this.updateBatchById(prodList);
//        }

        //****** mysql的锁了  行锁   innodb 才有行锁   myisam 表锁  直接发update 即可  这个是当前mysql主机只有一台的时候 是安全的
        //****** update prod set total_stock = total_stock + count where prod_id = prodId
        prodEsChanges.forEach(prodEsChange -> {
            Long prodId = prodEsChange.getProdId();
            Integer count = prodEsChange.getCount();
            int i = prodMapper.changeProdStock(prodId, count);
            if (i <= 0) {
                throw new IllegalArgumentException("库存不足");
            }
        });
        List<SkuChange> skuChanges = stockChange.getSkuChanges();
        skuChanges.forEach(skuChange -> {
            Long skuId = skuChange.getSkuId();
            Integer count = skuChange.getCount();
            int i = skuMapper.changeSkuStock(skuId, count);
            if (i <= 0) {
                throw new IllegalArgumentException("库存不足");
            }
        });
        return true;
    }
}


